vectorbt量化策略开发与回测实战(布林线策略)¶

量化小组学习1-杨欣琪

  • 本教程将带你系统学习如何用vectorbt实现布林线量化策略,包括数据获取与处理、布林线信号生成、回测与绩效分析、参数优化等。

  • vectorbt 是一个基于 Python 的高性能量化回测与分析库,专为策略研究、参数优化和大规模回测设计。它支持灵活的数据处理、指标计算、信号生成、组合回测与可视化,能够帮助量化研究者和交易员高效地开发和评估各类金融策略。vectorbt 兼容 pandas 和 NumPy,易于与主流数据科学工具集成,适合快速原型开发和批量参数扫描。

  • 我也调研了其他回测库比较轻量库例如 Backtesting 发现回测数据量大了就非常缓慢(应该是没有用NumPy写),总体使用不如vectorbt

No description has been provided for this image

1. 数据获取与处理(Data)¶

本节介绍如何获取和处理金融时间序列数据。以本地CSV文件为例,演示K线数据的读取与重采样。

在实际的量化过程中还可能需要从数据库、API接口或其他地方获取数据。

In [80]:
import os
import pandas as pd
import plotly.io as pio
pio.renderers.default = 'notebook'

csv_path = '/home/xqyang/code/quant/TS/btcusd_1-min_data.csv'  # 请替换为你的数据路径
assert os.path.exists(csv_path), f"文件不存在: {csv_path}"

df = pd.read_csv(csv_path)

df['Datetime'] = pd.to_datetime(df['Timestamp'], unit='s')
df = df.set_index('Datetime')
df = df.sort_index()  # 保证索引单调递增,便于切片

# 选择时间范围:2024年1月1日-2024年6月30日(用loc避免KeyError)
df = df.loc[(df.index >= '2024-03-01') & (df.index <= '2024-03-15')]

# 选择K线粒度
TIMEFRAME = '5min'  # 可选: '1min', '5min', '1H', '1D'等
ohlcv = df[['Open', 'High', 'Low', 'Close', 'Volume']].resample(TIMEFRAME).agg({
    'Open': 'first',
    'High': 'max',
    'Low': 'min',
    'Close': 'last',
    'Volume': 'sum'
}).dropna()
close = ohlcv['Close']
ohlcv.tail()
Out[80]:
Open High Low Close Volume
Datetime
2024-03-14 23:40:00 68823.0 69230.0 68555.0 68922.0 98.832549
2024-03-14 23:45:00 68905.0 69487.0 68864.0 69336.0 45.376130
2024-03-14 23:50:00 69339.0 69800.0 69274.0 69701.0 80.362212
2024-03-14 23:55:00 69675.0 69709.0 69177.0 69339.0 122.128794
2024-03-15 00:00:00 69284.0 69586.0 69251.0 69535.0 6.501516

2. 策略设计与信号生成(布林线策略)¶

本节以布林线突破策略为例,介绍如何用vectorbt生成布林线信号。

  • 上穿上轨做多,下穿中轨平多
  • 下穿下轨做空,上穿中轨平空

布林线策略设计逻辑说明¶

布林线(Bollinger Bands)是一种基于统计学原理的技术分析工具,主要用于衡量价格的波动性和判断超买超卖状态。其核心思想如下:

  • 中轨:通常为N日移动平均线,反映价格的趋势。
  • 上轨/下轨:分别为中轨加减K倍标准差,动态反映价格的波动区间。

策略逻辑设计¶

  1. 趋势突破信号

    • 当价格上穿上轨,说明市场波动性增强且价格强势突破历史波动区间,视为多头信号(做多开仓)。
    • 当价格下穿下轨,说明市场波动性增强且价格弱势跌破历史波动区间,视为空头信号(做空开仓)。
  2. 趋势反转/止盈信号

    • 多头持仓时,若价格回落下穿中轨,说明上涨动能减弱,趋势可能反转,选择平多。
    • 空头持仓时,若价格反弹上穿中轨,说明下跌动能减弱,趋势可能反转,选择平空。

设计原因¶

  • 动态适应市场波动:布林线宽度随市场波动自动调整,能适应不同市场环境。
  • 捕捉趋势行情:突破布林带往往伴随趋势行情启动,策略能及时跟随。
  • 风险控制:以中轨作为止盈/止损线,有助于锁定利润或及时止损,避免趋势反转带来的损失。

综上,布林线策略兼具趋势跟踪和风险控制能力,适合震荡和趋势行情下的量化交易。

In [81]:
import vectorbt as vbt
import numpy as np

# 布林线参数
n = 200 # 最近200根5分钟K线的数据来计算均线和标准差 200 × 5 = 1000 分钟(约16.7小时)
m = 3  # m倍标准差

# 计算布林线
median = close.rolling(n, min_periods=1).mean()
std = close.rolling(n, min_periods=1).std(ddof=0)
upper = median + m * std
lower = median - m * std

# 做多信号:收盘价上穿上轨
entries_long = (close > upper) & (close.shift(1) <= upper.shift(1))
# 做多平仓:收盘价下穿中轨
exits_long = (close < median) & (close.shift(1) >= median.shift(1))

# 做空信号:收盘价下穿下轨
entries_short = (close < lower) & (close.shift(1) >= lower.shift(1))
# 做空平仓:收盘价上穿中轨
exits_short = (close > median) & (close.shift(1) <= median.shift(1))

# 合并信号
entries = entries_long | entries_short
exits = exits_long | exits_short

# 信号DataFrame展示
signal_df = pd.DataFrame({
    'close': close,
    'median': median,
    'upper': upper,
    'lower': lower,
    'long_entry': entries_long,
    'long_exit': exits_long,
    'short_entry': entries_short,
    'short_exit': exits_short
})
signal_df.tail()
Out[81]:
close median upper lower long_entry long_exit short_entry short_exit
Datetime
2024-03-14 23:40:00 68922.0 72341.800 75957.139345 68726.460655 False False False False
2024-03-14 23:45:00 69336.0 72322.665 75989.217077 68656.112923 False False False False
2024-03-14 23:50:00 69701.0 72305.035 76008.181707 68601.888293 False False False False
2024-03-14 23:55:00 69339.0 72285.690 76036.572333 68534.807667 False False False False
2024-03-15 00:00:00 69535.0 72266.960 76056.665182 68477.254818 False False False False

3. 回测与绩效分析(Backtesting)¶

本节将利用 vectorbt 的 Portfolio.from_signals 方法进行回测,并分析策略表现。

Portfolio.from_signals 是 vectorbt 中用于基于买卖信号快速回测策略的核心方法。常用参数说明如下:

  • close:价格序列(如收盘价)。
  • entries:做多开仓信号(布尔序列)。
  • exits:做多平仓信号(布尔序列)。
  • short_entries:做空开仓信号(布尔序列)。
  • short_exits:做空平仓信号(布尔序列)。
  • init_cash:初始资金。
  • fees:手续费率(如 0.001 表示千分之一)。
  • slippage:滑点(如 0.001 表示千分之一)。
  • direction:交易方向,'longonly'(仅做多)、'shortonly'(仅做空)、'both'(双向)。

该方法会自动处理信号、资金、手续费等,生成完整的回测结果和丰富的绩效指标,便于后续分析。

In [82]:
# 构建并回测布林线策略
portfolio = vbt.Portfolio.from_signals(
    close,
    entries=entries,
    exits=exits,
    short_entries=entries_short,
    short_exits=exits_short,
    init_cash=100000,
    fees=0.001,
    slippage=0.001,
    direction='both'
)

# 查看全部绩效指标
stats = portfolio.stats()

# 英文到中文的映射
stats_cn_map = {
    'Start': '开始时间',
    'End': '结束时间',
    'Period': '回测周期',
    'Start Value': '初始资金',
    'End Value': '结束资金',
    'Total Return [%]': '总收益率 [%]',
    'Benchmark Return [%]': '基准收益率 [%]',
    'Max Gross Exposure [%]': '最大总风险敞口 [%]',
    'Total Fees Paid': '总手续费',
    'Max Drawdown [%]': '最大回撤 [%]',
    'Max Drawdown Duration': '最大回撤持续时间',
    'Total Trades': '总交易次数',
    'Total Closed Trades': '已平仓交易数',
    'Total Open Trades': '未平仓交易数',
    'Open Trade PnL': '未平仓盈亏',
    'Win Rate [%]': '胜率 [%]',
    'Best Trade [%]': '最佳交易 [%]',
    'Worst Trade [%]': '最差交易 [%]',
    'Avg Winning Trade [%]': '平均盈利交易 [%]',
    'Avg Losing Trade [%]': '平均亏损交易 [%]',
    'Avg Winning Trade Duration': '平均盈利持仓时间',
    'Avg Losing Trade Duration': '平均亏损持仓时间',
    'Profit Factor': '盈利因子',
    'Expectancy': '期望收益',
    'Sharpe Ratio': '夏普比率',
    'Calmar Ratio': '卡玛比率',
    'Omega Ratio': '欧米伽比率',
    'Sortino Ratio': '索提诺比率'
}
stats.index = [stats_cn_map.get(i, i) for i in stats.index]
display(stats)

# 查看部分交易明细
portfolio.trades.records_readable.tail()
/tmp/ipykernel_900975/1969587262.py:2: UserWarning:

direction has no effect if short_entries and short_exits are set

开始时间           2024-03-01 00:00:00
结束时间           2024-03-15 00:00:00
回测周期              14 days 00:05:00
初始资金                      100000.0
结束资金                  99783.430591
总收益率 [%]                 -0.216569
基准收益率 [%]                14.585393
最大总风险敞口 [%]                  100.0
总手续费                   1195.279918
最大回撤 [%]                  7.739755
最大回撤持续时间           9 days 17:10:00
总交易次数                            6
已平仓交易数                           6
未平仓交易数                           0
未平仓盈亏                          0.0
胜率 [%]                        50.0
最佳交易 [%]                  2.036248
最差交易 [%]                 -2.241125
平均盈利交易 [%]                1.308168
平均亏损交易 [%]               -1.355226
平均盈利持仓时间           0 days 17:05:00
平均亏损持仓时间           0 days 07:41:40
盈利因子                      0.947118
期望收益                    -36.094902
夏普比率                       0.02886
卡玛比率                     -0.709879
欧米伽比率                     1.000583
索提诺比率                     0.036011
Name: Close, dtype: object
Out[82]:
Exit Trade Id Column Size Entry Timestamp Avg Entry Price Entry Fees Exit Timestamp Avg Exit Price Exit Fees PnL Return Direction Status Position Id
1 1 Close 1.538221 2024-03-04 13:05:00 64816.752 99.702497 2024-03-05 10:00:00 66267.666 101.934324 2030.189747 0.020362 Long Closed 1
2 2 Close 1.496041 2024-03-07 19:35:00 67999.932 101.730659 2024-03-08 04:15:00 67028.904 100.277962 -1654.705930 -0.016266 Long Closed 2
3 3 Close 1.470259 2024-03-08 19:05:00 68068.000 100.077606 2024-03-08 21:00:00 66677.256 98.032852 -2242.864676 -0.022411 Long Closed 3
4 4 Close 1.421476 2024-03-10 06:00:00 68827.759 97.836982 2024-03-10 18:40:00 69011.919 98.098761 65.843209 0.000673 Long Closed 4
5 5 Close 1.386596 2024-03-11 11:10:00 70606.536 97.902759 2024-03-12 04:50:00 72034.893 99.883314 1782.768414 0.018210 Long Closed 5

4. 可视化分析¶

本节将展示布林线策略的可视化效果,包括:

  • K线图(蜡烛图)
  • 布林线(中轨、上轨、下轨)
  • 买入/卖出信号点
  • 资金曲线
In [83]:
# 可视化K线、布林线和买卖信号
import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Candlestick(
    x=ohlcv.index,
    open=ohlcv['Open'],
    high=ohlcv['High'],
    low=ohlcv['Low'],
    close=ohlcv['Close'],
    name='K线'
))
fig.add_trace(go.Scatter(x=close.index, y=median, line=dict(color='blue'), name='布林中轨'))
fig.add_trace(go.Scatter(x=close.index, y=upper, line=dict(color='green'), name='布林上轨'))
fig.add_trace(go.Scatter(x=close.index, y=lower, line=dict(color='red'), name='布林下轨'))

# 标记做多/做空信号
fig.add_trace(go.Scatter(x=close.index[entries_long], y=close[entries_long], mode='markers', marker=dict(color='orange', symbol='triangle-up', size=10), name='做多开仓'))
fig.add_trace(go.Scatter(x=close.index[exits_long], y=close[exits_long], mode='markers', marker=dict(color='purple', symbol='circle', size=8), name='做多平仓'))
fig.add_trace(go.Scatter(x=close.index[entries_short], y=close[entries_short], mode='markers', marker=dict(color='cyan', symbol='triangle-down', size=10), name='做空开仓'))
fig.add_trace(go.Scatter(x=close.index[exits_short], y=close[exits_short], mode='markers', marker=dict(color='magenta', symbol='circle', size=8), name='做空平仓'))

fig.update_layout(title='布林线策略K线图', xaxis_title='时间', yaxis_title='价格')
fig.write_html("bollinger_signal.html")
fig
In [84]:
fig_value = go.Figure()
fig_value.add_trace(go.Scatter(
    x=portfolio.value().index,
    y=portfolio.value().values,
    mode='lines',
    name='资金曲线'
))
fig_value.update_layout(
    title='Portfolio Value',
    xaxis_title='时间',
    yaxis_title='账户价值',
    template='plotly_white'
)
fig_value.write_html("portfolio_value_bollinger.html")
fig_value

5. 参数优化(Optimization)¶

通过参数扫描,寻找最优布林线窗口和倍数组合。

In [85]:
# 布林线参数优化示例
import numpy as np

n_range = np.arange(100, 501, 20)  # 窗口长度
m_range = np.arange(2.0, 5.1, 0.2)  # 倍数

results = np.zeros((len(n_range), len(m_range)))
for i, n_val in enumerate(n_range):
    for j, m_val in enumerate(m_range):
        med = close.rolling(int(n_val), min_periods=1).mean()
        st = close.rolling(int(n_val), min_periods=1).std(ddof=0)
        up = med + m_val * st
        low = med - m_val * st
        ent_long = (close > up) & (close.shift(1) <= up.shift(1))
        ext_long = (close < med) & (close.shift(1) >= med.shift(1))
        ent_short = (close < low) & (close.shift(1) >= low.shift(1))
        ext_short = (close > med) & (close.shift(1) <= med.shift(1))
        ent = ent_long | ent_short
        ext = ext_long | ext_short
        pf = vbt.Portfolio.from_signals(
            close,
            entries=ent,
            exits=ext,
            short_entries=ent_short,
            short_exits=ext_short,
            init_cash=100000,
            fees=0.001,
            slippage=0.001,
            direction='both'
        )
        results[i, j] = pf.total_return()

import plotly.graph_objects as go
fig = go.Figure(data=go.Surface(
    z=results,
    x=n_range,
    y=m_range,
    colorscale='Viridis'
))
fig.update_layout(
    title='布林线参数优化三维收益图',
    scene=dict(
        xaxis_title='n(窗口)',
        yaxis_title='m(倍数)',
        zaxis_title='Total Return'
    )
)
fig.write_html("bollinger_optimization_surface.html")
fig
/tmp/ipykernel_900975/3479930363.py:20: UserWarning:

direction has no effect if short_entries and short_exits are set

  • 从上图我们可以看到在寻找合适的参数的情况下,我们也能得到4%的收益,所以量化和机器学习一样需要很多调参工作
  • 和学习一样,有的参数可能会存在局部最优和全局最优的情况,在图上我们可以看山峰的情况